package de.idgard.android.proxy;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;

public class IDGardProxyClientInputStream extends BufferedInputStream {

	private String buf;
	private int nRead = 0;
	private String line;
	private int headerLength = 0;
	private int contentLength = 0;
	private boolean body = false;

	private static IDGardProxyServer server;

	private IDGardProxyHTTPSession connection;
	private InetAddress remoteHostAddress;
	private String remoteHostName;
	private boolean ssl = false;

	private String errordescription;
	private int statuscode;

	public String url;
	public String method;
	public int HTTPversion;
	public boolean ipv6reference;
	public int remotePort = 0;
	public int post_data_len = 0;

	public int getHeaderLength() {
		return headerLength;
	}

	public InetAddress getRemoteHost() {
		return remoteHostAddress;
	}

	public String getRemoteHostName() {
		return remoteHostName;
	}

	private String idgardUserAgent;

	public IDGardProxyClientInputStream(IDGardProxyServer server, IDGardProxyHTTPSession connection, InputStream a, String idgardUserAgent) {
		super(a);
		IDGardProxyClientInputStream.server = server;
		this.connection = connection;
		this.idgardUserAgent = idgardUserAgent;
	}

	public int read(byte[] a) throws IOException {
		statuscode = IDGardProxyHTTPSession.SC_OK;

		if (ssl) return super.read(a);

		if (server == null) {
			throw new IOException("Stream closed");
		}

		boolean cookies_enabled = server.enableCookiesByDefault();
		boolean start_line = true;
		int nChars;

		String rq = "";
		headerLength = 0;
		post_data_len = 0;
		contentLength = 0;
		boolean userAgentFound = false;
		nChars = getLine();
		buf = line;

		while (nChars != -1 && nChars > 2) {
			if (start_line) {
				start_line = false;
				int methodID = server.getHttpMethod(buf);
				switch (methodID) {
				case -1:
					statuscode = IDGardProxyHTTPSession.SC_NOT_SUPPORTED;
					break;
				case 2:
					ssl = true;
				default:
					InetAddress host = parseRequest(buf, methodID);
					if (statuscode != IDGardProxyHTTPSession.SC_OK) break;

					if (!server.useProxy && !ssl) {
						buf = method + " " + url + " " + server.getHttpVersion() + "\r\n";
						nRead = buf.length();
					}
					if ((server.useProxy && (connection != null && connection.notConnected())) || !remoteHostAddress.equals(host)) {
						if (server.debug) {
							server.writeLog("connect: " + remoteHostAddress + " -> " + host);
						}
						statuscode = IDGardProxyHTTPSession.SC_CONNECTING_TO_HOST;
						remoteHostAddress = host;
					}
				}
			} else {
				/*-----------------------------------------------
				 * Content-Length parsing
				 *-----------------------------------------------*/
				if (server.startsWith(buf.toUpperCase(), "CONTENT-LENGTH")) {
					String clen = buf.substring(16);
					if (clen.indexOf("\r") != -1)
						clen = clen.substring(0, clen.indexOf("\r"));
					else if (clen.indexOf("\n") != -1) clen = clen.substring(0, clen.indexOf("\n"));
					try {
						contentLength = Integer.parseInt(clen);
					} catch (NumberFormatException e) {
						statuscode = IDGardProxyHTTPSession.SC_CLIENT_ERROR;
					}
					if (server.debug) server.writeLog("read_f: content_len: " + contentLength);
					if (!ssl) body = true;
				} else if (server.startsWith(buf, "Proxy-Connection:")) {
					if (!server.useProxy)
						buf = null;
					else {
						buf = "Proxy-Connection: Keep-Alive\r\n";
						nRead = buf.length();
					}
				}

				else if (server.startsWith(buf, "Cookie:")) {
					if (!cookies_enabled) buf = null;
				}
				if (server.filter_http) {
					if (server.startsWith(buf, "User-Agent")) // changes
					// User-Agent
					{
						userAgentFound = true;
						nRead = buf.length();
					}
				}
			}

			if (buf != null) {
				rq += buf;
				headerLength += nRead;
			}
			nChars = getLine();
			buf = line;
		}

		if (nChars != -1) {
			if (nChars > 0) {
				rq += buf;
				headerLength += nRead;
			}

			if (headerLength == 0) {
				statuscode = IDGardProxyHTTPSession.SC_CONNECTION_CLOSED;
			}

			for (int i = 0; i < headerLength; i++)
				a[i] = (byte) rq.charAt(i);

			if (body) {// read the body, if "Content-Length" given
				post_data_len = 0;
				while (post_data_len < contentLength) {
					a[headerLength + post_data_len] = (byte) read(); // writes
																		// data
					// into the
					// array
					post_data_len++;
				}
				headerLength += contentLength; // add the body-length to the
				// header-length
				body = false;
			}
		}

		if (!userAgentFound && ssl) {
			// System.out.println("NEW APPENDED");
			buf = "User-Agent: " + idgardUserAgent + "\r\nProxy-Connection: Keep-Alive\r\nHost: \r\n";
			headerLength += buf.length();
		}

		return (statuscode == IDGardProxyHTTPSession.SC_OK) ? headerLength : -1;

	}

	/**
	 * reads a line
	 * 
	 * @exception IOException
	 * @return number of chars in the line
	 */
	public int getLine() throws IOException {
		int c = 0;
		line = "";
		nRead = 0;
		while (c != '\n') {
			c = read();
			if (c != -1) {
				line += (char) c;
				nRead++;
			} else
				break;
		}
		return nRead;
	}

	/**
	 * Parser for the first line from the HTTP request. Sets up the URL, method
	 * and remote host name.
	 * 
	 * @return an InetAddress for the host name, null on errors with a
	 *         statuscode!=SC_OK
	 */
	public InetAddress parseRequest(String a, int method_index) {

		int pos;
		int ipv6bracket;

		String f = "";
		String r_host_name = "";
		String r_port = "";

		url = "";

		if (ssl) {
			// remove CONNECT
			f = a.substring(8);
		} else {
			method = a.substring(0, a.indexOf(" ")); // first word in the line
			pos = a.indexOf(":"); // locate first ":"

			// Proxy request
			f = a.substring(pos + 3); // removes "http://"
		}
		// Strip white spaces
		f = f.replace("\r", "").replace("\n", "");

		int versionp = f.indexOf("HTTP/");
		String HTTPversionRaw;

		// length of "HTTP/x.x": 8 chars
		if (versionp == (f.length() - 8)) {
			// Detect the HTTP version
			HTTPversionRaw = f.substring(versionp + 5);
			if (HTTPversionRaw.equals("1.1"))
				HTTPversion = 1;
			else if (HTTPversionRaw.equals("1.0")) HTTPversion = 0;

			// remove " HTTP/x.x"
			f = f.substring(0, versionp - 1);
			if (server.debug) server.writeLog("-->" + f + "<--");
		} else {
			// bad request: no "HTTP/xxx" at the end of the line
			HTTPversionRaw = "";
		}

		pos = f.indexOf("/"); // locate the first slash
		if (pos != -1) {
			url = f.substring(pos); // saves path without host name
			r_host_name = f.substring(0, pos); // reduce string to the host name
		} else {
			url = "/";
			r_host_name = f;
		}

		if (server.debug) server.writeLog("#->" + url);

		// search for bracket in host name (IPv6, RFC 2732)
		ipv6bracket = r_host_name.indexOf("[");
		if (ipv6bracket == 0) {
			r_host_name = r_host_name.substring(1);
			ipv6bracket = r_host_name.indexOf("]");
			r_port = r_host_name.substring(ipv6bracket + 1);
			r_host_name = r_host_name.substring(0, ipv6bracket);

			if (server.debug) server.writeLog("ipv6 bracket ->" + r_host_name + "<--");

			// URL with brackets, must be IPv6 address
			ipv6reference = true;

			// detect the remote port number, if any
			pos = r_port.indexOf(":");
			if (pos != -1) {
				r_port = r_port.substring(pos + 1);
			} else {
				r_port = null;
			}

		} else {
			// no IPv6 reference with brackets according to RFC 2732
			ipv6reference = false;
			pos = r_host_name.indexOf(":");
			if (pos != -1) {
				r_port = r_host_name.substring(pos + 1);
				r_host_name = r_host_name.substring(0, pos);
			} else
				r_port = null;
		}

		// Port number: parse String and convert to integer
		if (r_port != null && !r_port.equals("")) {
			try {
				remotePort = Integer.parseInt(r_port);
			} catch (NumberFormatException e_get_host) {
				if (server.debug) server.writeLog("get_Host :" + e_get_host + " Failed to parse remote port numer!");
				remotePort = 80;
			}
		} else
			remotePort = 80;

		if (server.debug) server.writeLog(method + " " + url + " " + HTTPversionRaw);

		remoteHostName = r_host_name;
		InetAddress address = null;

		try {
			address = InetAddress.getByName(remoteHostName);

		} catch (UnknownHostException e_u_host) {
			if (!server.useProxy) statuscode = IDGardProxyHTTPSession.SC_HOST_NOT_FOUND;
		}

		if (remotePort == server.port && address != null && address.equals(connection.serveraddress)) {
			if (method_index > 0) {
				statuscode = IDGardProxyHTTPSession.SC_INTERNAL_SERVER_ERROR;
				errordescription = "This WWW proxy supports only the \"GET\" method while acting as webserver.";
			} else
				statuscode = IDGardProxyHTTPSession.SC_FILE_REQUEST;
		}

		return address;
	}

	public boolean isTunnel() {
		return ssl;
	}

	/**
	 * @return the full qualified URL of the actual request.
	 * @since 0.4.0
	 */
	public String getFullUrl() {
		return "http" + (ssl ? "s" : "") + "://" + (ipv6reference ? "[" + getRemoteHostName() + "]" : getRemoteHostName()) + (remotePort != 80 ? (":" + remotePort) : "")
				+ url;
	}

	/**
	 * @return status-code for the current request
	 * @since 0.3.5
	 */
	public int getStatusCode() {
		return statuscode;
	}

	/**
	 * @return the (optional) error description for this request
	 */
	public String getErrorDescription() {
		return errordescription;
	}
}
